/**
 * Copyright (c) 2022 Presto Labs Pte. Ltd.
 * Author: Ju
 */

#pragma once

#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>

#include "coin2/base/time.h"

namespace coin2::base::influxdb {

using BigFieldsType = std::unordered_map<std::string, std::string>;

struct LineProtocol {
  std::shared_ptr<std::string> header{nullptr};
  BigFieldsType *buffer{nullptr};
  int64_t ts{-1};

  LineProtocol() = default;
  LineProtocol(const LineProtocol &rhs);
  LineProtocol(LineProtocol &&rhs);
  ~LineProtocol();
};

class Builder {
 public:
  Builder() = default;
  Builder &Measurement(std::string m);
  Builder &Tag(const std::string &key, std::string v);
  Builder &Timestamp(int64_t ts);

  // for std::string
  template <typename T,
            typename std::enable_if<
                std::is_same_v<typename std::decay<T>::type, std::string>,
                int>::type = 0>
  Builder &TypeField(const std::string &key, T &&v) {
    std::ostringstream oss;
    oss << '"' << Escape(v) << '"';
    fields_.insert(std::make_pair(Escape(key), oss.str()));
    return *this;
  }

  // for int,float
  template <typename T,
            typename std::enable_if<
                std::is_integral_v<typename std::decay<T>::type> ||
                    std::is_floating_point_v<typename std::decay<T>::type>,
                int>::type = 0>
  Builder &TypeField(const std::string &key, T &&v) {
    fields_.insert(std::make_pair(Escape(key), std::to_string(v)));
    return *this;
  }

  void Freeze();

  LineProtocol Build();

 private:
  void Reset();
  std::string BuildHeader();

  // for string field
  // escape: double quote
  std::string Escape(const std::string &s);

 private:
  /*
        Measurement(tag)
        /   \
      field field

      example:
      Measurement = "exchange"
      tag = "huobi","order" | tag = "huobi","feed"
      field = ("raw_msg_num",100),("order_num",100)
  */
  bool freeze_{false};
  std::shared_ptr<std::string> header_{nullptr};
  static constexpr int64_t kInitialTs = -1;
  std::string measurement_;
  // key only supports contiguous strings(excluding symbols)
  std::unordered_map<std::string, std::string> tags_;
  std::unordered_map<std::string, std::string> fields_;
  int64_t ts_ = -1;
};

inline Builder &Builder::Measurement(std::string m) {
  measurement_ = Escape(m);
  return *this;
}

inline Builder &Builder::Timestamp(int64_t ts) {
  ts_ = ts;
  return *this;
}

}  // namespace coin2::base::influxdb